# coding: utf-8


import math
from decimal import Decimal

from sqlalchemy import cast, Integer, DECIMAL

from app.exceptions import NotProductError
from app.products.constant import SceneType, CalculateItem, AssembleWay, \
    Transport, ColorType, scene_to_waterproof, SECTION_LENGTH, REDUNDANCY, \
    MM_TO_M, SINGLE_RETICLE_PIXEL, InstallWay, TOTAL_PIXEL, STEEL_PRICE, \
    INCREMENT, POWER_MAX_LOAD_RATE
from app.products.models import ProductType, ProductTemplate, ProductToParam


class Quotation(object):

    def __init__(self, source):
        # 输入条件
        self.source = source
        # 输入高
        self._high = source.high.data * 1000
        # 输入宽
        self._wide = source.wide.data * 1000
        # 像素间距
        self._spacing = source.spacing.data
        # 户内外
        self._scene_type = source.scene_type.data
        # 颜色
        self._color = source.color.data
        # 组装方式
        self._assemble_way = source.assemble_way.data
        # 安装方式
        self._install_way = source.install_way.data
        # 传输方式
        self._transport = source.transport.data
        # 是否防水
        self._waterproof = scene_to_waterproof.get(self._scene_type)
        # 离地高度
        self._ground_high = source.ground_high.data

        self.__module_product = None
        # 模组高
        self.__m_high = 0
        # 模组宽
        self.__m_wide = 0
        # 模组宽边分辨率
        self.__m_w_resolution = 0
        # 模组高边分辨率
        self.__m_h_resolution = 0
        # 模组额定功率
        self.__m_power_rating = 0

        # 箱体高
        self.__box_high = 0
        # 箱体宽
        self.__box_wide = 0
        self.box_product = None

        # 接收卡数量
        self._receive_count = 0
        # 电源线个数
        self._power_line_count = 0

        # 普通箱体a宽边个数
        self.a_wide_count = 0
        # 普通箱体a高边个数
        self.a_high_count = 0
        # 箱体b宽
        self.b_wide = 0
        # 箱体c高
        self.c_high = 0

    @property
    def a_module_count(self):
        """
        A类箱体模组个数
       （箱体宽/模组宽）* (箱体高/模组高)
        :return:
        """
        return (
            (self.__box_high / self.module_high) *
            (self.__box_wide / self.module_wide)
        )

    @property
    def b_module_count(self):
        """
        B类箱体模组个数
        （箱体B宽/模组宽） * (箱体A高/模组高)
        :return:
        """
        return (
            (self.b_wide / self.module_wide) *
            (self.__box_high / self.module_high)
        )

    @property
    def c_module_count(self):
        """
        C类箱体模组个数
        （箱体宽A/模组宽）* (箱体c高/模组高)
        :return:
        """
        return (
            (self.c_high / self.module_high) *
            (self.__box_wide / self.module_wide)
        )

    @property
    def d_module_count(self):
        """
        D类箱体模组个数
        (箱体B宽/模组宽) * (箱体C高/模组高)
        :return:
        """
        return (
            (self.b_wide / self.module_wide) *
            (self.c_high / self.module_high)
        )

    @property
    def module_high_resolution(self):
        """
        模组高边分辨率
        :return:
        """
        if not self.__m_h_resolution:
            self.__set_module_high_and_wide()
        return self.__m_h_resolution

    @property
    def module_wide_resolution(self):
        """
        模组宽边边分辨率
        :return:
        """
        if not self.__m_w_resolution:
            self.__set_module_high_and_wide()
        return self.__m_w_resolution

    @property
    def module_high(self):
        if not self.__m_high:
            self.__set_module_high_and_wide()
        return self.__m_high

    @property
    def module_wide(self):
        if not self.__m_wide:
            self.__set_module_high_and_wide()
        return self.__m_wide

    @property
    def module_power_rating(self):
        """
        模组额定功率
        :return:
        """
        if not self.__m_power_rating:
            self.__set_module_high_and_wide()
        return self.__m_power_rating

    @property
    def m_high_count(self):
        """
        模组高边个数
        :return:
        """
        # FIXME by ld_wu 2017-08-18 租赁箱体模组计算方式必须满足箱体的倍数
        if self._assemble_way == AssembleWay.RENTAL:
            if not self.box_product:
                product_type = ProductType.get_by_args(name='组装-租赁箱体')
                products = ProductTemplate.query.filter(
                    ProductTemplate.product_type_id == product_type.id
                )
                self.box_product = self.__get_box(products)
            return math.ceil(
                math.ceil(
                    self._high / self.__box_high) *
                self.__box_high / self.module_high
            )
        return math.ceil(self._high/self.module_high)

    @property
    def m_wide_count(self):
        """
        模组宽边个数
        :return:
        """
        # FIXME by ld_wu 2017-08-18 租赁箱体模组计算方式必须满足箱体的倍数
        if self._assemble_way == AssembleWay.RENTAL:
            if not self.box_product:
                product_type = ProductType.get_by_args(name='组装-租赁箱体')
                products = ProductTemplate.query.filter(
                    ProductTemplate.product_type_id == product_type.id
                )
                self.box_product = self.__get_box(products)
            return math.ceil(
                math.ceil(
                    self._wide / self.__box_wide) *
                self.__box_wide / self.module_wide
            )
        return math.ceil(self._wide/self.module_wide)

    @property
    def screen_high(self):
        """
        屏幕高(实际高)
        :return:
        """
        return self.m_high_count * self.module_high

    @property
    def screen_wide(self):
        """
        屏幕宽(实际宽)
        :return:
        """
        return self.m_wide_count * self.module_wide

    @staticmethod
    def round_six(value, base=1):
        """
        基于基数的五舍六入
        4, 1.2 = round_six(5.2, 1)
        6, 0.4 = round_six(5.2, 0.6)
        :return:
        """
        integral, leave = divmod(value, base)
        # 五舍六入
        if (leave / base) > 0.5:
            return integral, leave
        return integral-1, leave + base

    def common_box_counts(self):
        self.a_wide_count, self.b_wide = self.round_six(
            self.screen_wide, self.__box_wide
        )
        self.a_high_count, self.c_high = self.round_six(
            self.screen_high, self.__box_high
        )

    def __set_module_high_and_wide(self):
        m_high = 0
        m_wide = 0
        # 获取宽高等计算项
        product_specs = ProductToParam.query.filter(
            ProductToParam.product_template_id == self.module_product.id,
            ProductToParam.calculate_item != CalculateItem.NIL
        )

        for each in product_specs:
            if each.calculate_item == CalculateItem.WIDE:
                m_wide = Decimal(each.value)
            elif each.calculate_item == CalculateItem.HIGH:
                m_high = Decimal(each.value)
            elif each.calculate_item == CalculateItem.POWER_RATING:
                self.__m_power_rating = Decimal(each.value)
            elif each.calculate_item == CalculateItem.W_RESOLUTION:
                self.__m_w_resolution = Decimal(each.value)
            elif each.calculate_item == CalculateItem.H_RESOLUTION:
                self.__m_h_resolution = Decimal(each.value)
        if not m_wide or not m_high:
            # 模组没有相应的计算项
            raise NotProductError(
                '模组-{}没有相应的计算项'.format(self.module_product.name)
            )
        self.__m_high = m_high
        self.__m_wide = m_wide

    @property
    def module_product(self):
        """
        匹配的模组产品
        :return:
        """
        if self.__module_product:
            return self.__module_product
        product_type = ProductType.get_by_args(name='模组')
        query = self.__filter(product_type)
        # 像素间距
        if self._spacing:
            query = query.join(
                ProductToParam, ProductTemplate.id == ProductToParam.product_template_id
            ).filter(
                ProductToParam.calculate_item == CalculateItem.SPACING,
                cast(ProductToParam.value, DECIMAL) <= Decimal(self._spacing)
            )
        self.__module_product = self.__get_product(query, '没有匹配的模组')
        return self.__module_product

    def __filter(self, product_type):
        """
        基本使用条件的filter
        :param product_type:
        :return:
        """
        query = ProductTemplate.query.filter(
            ProductTemplate.product_type_id == product_type.id
        )
        # 户内/户外
        if self._scene_type:
            query = query.filter(
                ProductTemplate.scene == self._scene_type
            )
        # 全彩/单色/双色
        if self._color:
            query = query.filter(ProductTemplate.color == self._color)

        # 是否防水
        if self._waterproof:
            query = query.filter(ProductTemplate.waterproof == self._waterproof)

        # 获取权重高的产品
        query = query.order_by(
            ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
        )
        return query

    @staticmethod
    def __get_product(query, msg='没有匹配的产品'):
        """
        获取相应的产品
        :param query:
        :return:
        """
        obj = query.first()

        if not obj:
            # 没有匹配的模组产品
            raise NotProductError(msg)
        return obj

    @staticmethod
    def __base_product(product_type_name):
        product_type = ProductType.get_by_args(name=product_type_name)
        product = ProductTemplate.query.filter(
            ProductTemplate.product_type_id == product_type.id
        ).order_by(
            ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
        ).first()
        if not product:
            raise NotProductError('没有匹配的{}'.format(product_type_name))
        return product

    def module_group(self):
        """
        create_by ld_wu 2017-08-11
        模组计算模型
        输入宽w、输入高h, 模组宽m_w、模组高m_h
        模组总个数: ceil(w/m_w) * ceil(h/m_h)
        return: 模组总个数 * price
        """
        return dict(
            price=self.module_product.suggest_price,
            quantity=self.m_high_count * self.m_wide_count,
            product_name=self.module_product.name,
            total=(
                self.module_product.suggest_price *
                self.m_high_count *
                self.m_wide_count),
            brand=self.module_product.brand,
            product_type='模组',
            spec='{}mm*{}mm'.format(self.module_wide, self.module_high)
        )

    def __box_compute(self, query):
        obj = self.__get_product(query, '没有匹配的箱体')
        self.box_product = obj
        product_specs = ProductToParam.query.filter(
            ProductToParam.product_template_id == obj.id,
            ProductToParam.calculate_item != CalculateItem.NIL
        )
        m_high = None
        m_wide = None
        for each in product_specs:
            if each.calculate_item == CalculateItem.WIDE:
                m_wide = Decimal(each.value)
            elif each.calculate_item == CalculateItem.HIGH:
                m_high = Decimal(each.value)
        if not m_wide or not m_high:
            # 模组没有相应的计算项
            raise NotProductError('箱体-{}没有相应的计算项'.format(obj.name))
        self.__box_wide = m_wide
        self.__box_high = m_high
        quantity = (
            math.ceil(self.screen_high / m_high) *
            math.ceil(self.screen_wide / m_wide)
        )
        self._power_line_count = quantity
        return dict(
            price=obj.suggest_price,
            quantity=quantity,
            product_name=obj.name,
            brand=obj.brand,
            total=quantity * obj.suggest_price
        )

    def original_box(self):
        """
        create_by ld_wu 2017-08-11
        原装箱体计算模型
        输入宽w、输入高h, 箱体宽b_w、箱体高b_h
        箱体总个数total: (w/b_w) * (h/b_h)
        return: total * price(产品价格)
        """
        product_type = ProductType.get_by_args(name='原装箱体')
        query = self.__filter(product_type)
        res = dict(product_type='原装箱体')
        res.update(self.__box_compute(query))
        return res

    def rental_box(self):
        """
        create_by ld_wu 2017-08-11
        组装租赁箱体计算模型
        输入宽w、输入高h, 箱体宽b_w、箱体高b_h
        箱体总个数total: (w/b_w) * (h/b_h)
        return: total * price(产品价格)
        """
        product_type = ProductType.get_by_args(name='组装-租赁箱体')
        products = ProductTemplate.query.filter(
            ProductTemplate.product_type_id == product_type.id
        )
        product = self.box_product or self.__get_box(products)
        quantity = (
            math.ceil(self.screen_high / self.__box_high) *
            math.ceil(self.screen_wide / self.__box_wide)
        )
        return dict(
            price=product.suggest_price,
            quantity=quantity,
            product_name=product.name,
            brand=product.brand,
            total=quantity * product.suggest_price,
            product_type='组装-租赁箱体'
        )

    def __get_box(self, products):
        box_high = 0
        box_wide = 0
        for item in products:
            product_specs = ProductToParam.query.filter(
                ProductToParam.product_template_id == item.id,
                ProductToParam.calculate_item != CalculateItem.NIL
            )
            for each in product_specs:
                if each.calculate_item == CalculateItem.WIDE:
                    box_wide = int(each.value)
                elif each.calculate_item == CalculateItem.HIGH:
                    box_high = int(each.value)
            if box_high and box_wide:
                _, wide_left = divmod(box_wide, self.module_high)
                _, hide_left = divmod(box_high, self.module_wide)
                if not wide_left and not hide_left:
                    self.__box_high = box_high
                    self.__box_wide = box_wide
                    self.box_product = item
                    return item
            box_high = 0
            box_wide = 0
        else:
            raise NotProductError('没有匹配的箱体')

    def common_box(self):
        """
        create_by ld_wu 2017-08-11
        组装普通箱体计算模型
        实际屏高 * 实际屏宽 * 普通箱体价格
        普通箱体：可以整除已选模组高宽的箱体
        """
        product_type = ProductType.get_by_args(name='组装-普通标准箱体')
        products = ProductTemplate.query.filter(
            ProductTemplate.product_type_id == product_type.id
        ).order_by(
            ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
        )

        product = self.__get_box(products)

        self.box_product = product
        quantity = (
            self.screen_wide *  # 毫米
            self.screen_high *  # 毫米
            MM_TO_M             # 需要转换为平方米
        )
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity='%.2f' % quantity,
            total=quantity * product.suggest_price,
            product_type='组装-普通标准箱体',
        )

    def box_fastener(self):
        """
        create_by ld_wu 2017-08-11
        箱体扣件计算模型
        (箱体宽边个数 + 1) * (箱体高边个数 + 1)
        """
        product = self.__base_product('箱体扣件')
        quantity = 0
        if self._assemble_way == AssembleWay.COMMON:
            # 普通箱体为a箱体高宽个数各加1，计算扣件时需要
            # 箱体高宽个数再加1然后相乘，所以需要加2
            quantity = (self.a_high_count + 2) * (self.a_wide_count + 2)
        elif self._assemble_way == AssembleWay.RENTAL:
            quantity = ((self.screen_high / self.__box_high + 1) *
                        (self.screen_wide / self.__box_wide + 1)
                        )
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity='%.2f' % quantity,
            total=quantity * product.suggest_price,
            product_type='箱体扣件',
        )

    def power_supply(self):
        """
        create_by ld_wu 2017-08-11
        电源计算模型
        """
        def common_price():
            product, power_rating = common_product()
            # A类箱体总电源数
            a_count = math.ceil(
                (self.a_module_count * self.module_power_rating) /
                (power_rating * max_load_rate)
            ) * self.a_high_count * self.a_wide_count
            # B类箱体总电源数
            b_count = math.ceil(
                (self.b_module_count * self.module_power_rating) /
                (power_rating * max_load_rate)
            ) * self.a_high_count
            # C类箱体总电源数
            c_count = math.ceil(
                (self.c_module_count * self.module_power_rating) /
                (power_rating * max_load_rate)
            ) * self.a_wide_count
            # D类箱体总电源数
            d_count = math.ceil(
                (self.d_module_count * self.module_power_rating) /
                (power_rating * max_load_rate)
            )
            quantity = (a_count + b_count + c_count + d_count)
            self._power_line_count = quantity
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                spec=power_rating
            )

        def rental_price():
            product, power_rating = rental_product()
            quantity = (
                (self.screen_high / self.__box_high) *
                (self.screen_wide / self.__box_wide)
            )
            self._power_line_count = quantity
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                spec=power_rating
            )

        def common_product(box=True):
            product = ProductTemplate.query.filter(
                ProductTemplate.product_type_id == product_type.id
            ).order_by(
                ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
            ).first()
            if not product:
                raise NotProductError('没有匹配的电源')
            if box:
                self.common_box_counts()
            product_spec = ProductToParam.query.filter(
                ProductToParam.product_template_id == product.id,
                ProductToParam.calculate_item == CalculateItem.POWER_RATING
            ).first()
            if not product_spec:
                raise NotProductError('电源-{}没有匹配的计算项'.format(product.name))
            return product, Decimal(product_spec.value)

        def rental_product():
            # 单箱体模组数
            box_m_count = (
                math.ceil(self.__box_high / self.module_high) *
                math.ceil(self.__box_wide / self.module_wide)
            )
            # 单箱体需要的总功率
            total_power = math.ceil(
                box_m_count * self.module_power_rating / REDUNDANCY
            )
            products = ProductTemplate.query.filter(
                ProductTemplate.product_type_id == product_type.id
            ).order_by(
                ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
            ).all()
            product_map = {each.id: each for each in products}
            self.common_box_counts()
            product_specs = ProductToParam.query.filter(
                ProductToParam.product_template_id.in_(product_map.keys()),
                ProductToParam.calculate_item == CalculateItem.POWER_RATING,
                cast(ProductToParam.value, Integer) >= total_power
            ).order_by(
                cast(ProductToParam.value, Integer)
            ).all()
            spec_map = {each.product_template_id: each for each in product_specs}
            for product in products:
                if product.id in spec_map:
                    return product, Decimal(spec_map[product.id].value)

            else:
                raise NotProductError('没有匹配的电源')

        def section_bar_price():
            product, power_rating = common_product(False)
            # 电源数量
            quantity = math.ceil(
                self.m_wide_count * self.m_high_count * self.module_power_rating
                / REDUNDANCY / power_rating
            )
            self._power_line_count = quantity
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                spec=power_rating
            )

        mapping = {
            AssembleWay.COMMON: common_price,
            AssembleWay.RENTAL: rental_price,
            AssembleWay.SECTION: section_bar_price
        }

        product_type = ProductType.get_by_args(name='电源')
        max_load_rate = Decimal(POWER_MAX_LOAD_RATE)

        res = mapping.get(self._assemble_way, lambda: dict())()
        if res:
            res['product_type'] = '电源'
            return res

    def receive_card(self):
        """
        create_by ld_wu 2017-08-11
        接收卡计算模型
        ciel(宽边总点/接收卡宽边点) * ciel(高边总点/接收卡高边点) * price
        """
        mapping = {
            Transport.ASYNC: '异步接收卡',
            Transport.SYNC: '接收卡'
        }

        product = self.__base_product(mapping[self._transport])

        product_specs = ProductToParam.query.filter(
            ProductToParam.product_template_id == product.id,
            ProductToParam.calculate_item != CalculateItem.NIL
        )
        high_pixel = None
        wide_pixel = None
        for each in product_specs:
            if each.calculate_item == CalculateItem.H_MAX_PIXEL:
                high_pixel = Decimal(each.value)
            elif each.calculate_item == CalculateItem.W_MAX_PIXEL:
                wide_pixel = Decimal(each.value)
        if not high_pixel or not wide_pixel:
            # 没有相应的计算项
            raise NotProductError(
                '产品-{}没有相应的计算项（带载点数）'.format(product.name)
            )

        # 高边点数
        h_total = self.m_high_count * self.module_high_resolution
        # 宽边点数
        w_total = self.m_wide_count * self.module_wide_resolution
        quantity = (
            math.ceil(h_total / high_pixel) *
            math.ceil(w_total / wide_pixel)
        )
        self._receive_count = quantity
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type=mapping[self._transport]
        )

    def sending_card(self):
        """
        create_by ld_wu 2017-08-11
        发送卡计算模型
        """

        mapping = {
            Transport.ASYNC: '异步发送卡',
            Transport.SYNC: '发送卡'
        }
        product = self.__base_product(mapping[self._transport])
        res = self.__cal_card_video(product)
        res['product_type'] = mapping[self._transport]
        return res

    def __cal_card_video(self, product):
        """
        发送卡、视频处理器、视频拼接器、异步控制器共用计算模型
        :return:
        """

        def cal_card_count(cal_total, cal_pixel, o_total):
            cal_count = math.ceil(cal_total / cal_pixel)
            cal_rel_pixel = math.ceil(cal_total / cal_count)
            o_rel_pixel = math.ceil(total_pixel / cal_rel_pixel * Decimal('0.9'))
            w_count = math.ceil(o_total / o_rel_pixel)
            return w_count * cal_count

        product_specs = ProductToParam.query.filter(
            ProductToParam.product_template_id == product.id,
            ProductToParam.calculate_item != CalculateItem.NIL
        )
        high_pixel = None
        wide_pixel = None
        total_pixel = None
        for each in product_specs:
            if each.calculate_item == CalculateItem.H_MAX_PIXEL:
                high_pixel = Decimal(each.value)
            elif each.calculate_item == CalculateItem.W_MAX_PIXEL:
                wide_pixel = Decimal(each.value)
            elif each.calculate_item == CalculateItem.PIXEL:
                total_pixel = Decimal(each.value)
        if not high_pixel or not wide_pixel or not total_pixel:
            # 没有相应的计算项
            raise NotProductError(
                '产品-{}没有相应的计算项（带载点数）'.format(product.name)
            )

        # 高边点数
        h_total = self.m_high_count * self.module_high_resolution
        # 宽边点数
        w_total = self.m_wide_count * self.module_wide_resolution
        quantity = min(
            cal_card_count(h_total, high_pixel, w_total),
            cal_card_count(w_total, wide_pixel, h_total)
        )
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price
        )

    def video_processor(self):
        """
        create_by ld_wu 2017-08-11
        视频处理器计算模型(与拼接器不共用)
        """
        total_pixel = (
            self.__m_w_resolution *
            self.m_wide_count *
            self.__m_h_resolution *
            self.m_high_count
        )
        if total_pixel <= TOTAL_PIXEL:
            product_type = ProductType.get_by_args(name='视频处理器')
            product = ProductTemplate.query.filter(
                ProductTemplate.product_type_id == product_type.id
            ).order_by(
                ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
            ).first()
            if not product:
                raise NotProductError('视频处理器没有匹配的产品')
            res = self.__cal_card_video(product)
            res['product_type'] = '视频处理器'
            return res
        return 0

    def video_splicer(self):
        """
        create_by ld_wu 2017-08-11
        视频拼接器计算模型
        """
        total_pixel = (
            self.__m_w_resolution *
            self.m_wide_count *
            self.__m_h_resolution *
            self.m_high_count
        )
        if total_pixel <= TOTAL_PIXEL:
            return 0
        product = self.__base_product('视频拼接器')
        res = self.__cal_card_video(product)

        res['product_type'] = '视频拼接器'
        return res

    def async_controller(self):
        """
        create_by ld_wu 2017-08-11
        异步控制器计算模型
        """
        product = self.__base_product('异步控制器')
        res = self.__cal_card_video(product)
        res['product_type'] = '异步控制器'
        return res

    def network_card(self):
        """
        create_by ld_wu 2017-08-11
        网卡计算模型
        """
        return 0

    def screw(self):
        """
        create_by ld_wu 2017-08-11
        螺丝计算模型
        需要数量：模组总个数 * 12
        """
        product = self.__base_product('螺丝')
        # 单个模组需要的螺丝数
        single_count = 12
        quantity = self.m_wide_count * self.m_high_count * single_count

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='螺丝'
        )

    def steel_structure(self):
        """
        create_by ld_wu 2017-08-11
        钢结构计算模型
        """
        def steel_price():
            try:
                steel_prices = STEEL_PRICE[self._scene_type][self._install_way]
                for _area, _price in steel_prices:
                    if _area is None:
                        return _price
                    elif area <= (_area * pow(10, 6)):
                        return _price
            except KeyError:
                return 0
        area = (self.screen_wide + INCREMENT) * (self.screen_high + INCREMENT)
        price = steel_price()
        quantity = area * MM_TO_M
        return dict(
            product_type='钢结构',
            brand='',
            price=price,
            quantity='%.2f' % quantity,
            total=quantity * price
        )

    def steel_structure_high(self):
        """
        create_by ld_wu 2017-08-11
        钢结构离地高度计算模型
        """
        area = (self.screen_wide + INCREMENT) * (self.screen_high + INCREMENT)

        def high_price():
            _quantity = (self._ground_high - base) * area * MM_TO_M
            return dict(
                product_type='钢结构离地高度加价',
                brand='',
                price=price,
                quantity='%.2f' % _quantity,
                total=_quantity * price
            )

        if self._install_way in [InstallWay.SINGLE, InstallWay.DOUBLE]:
            # 单立柱\双立柱的高度大于4米需要加收价格(50元/平)
            base = 4
            if self._ground_high > base:
                price = 50
                return high_price()
        elif self._install_way in [InstallWay.EMBED, InstallWay.WALL]:
            # 墙挂或者嵌入的方式高于15米需要加价（10元/平）
            base = 15
            if self._ground_high > base:
                price = 10
                return high_price()
        return 0

    def steel_structure_welding(self):
        """
        create_by ld_wu 2017-08-11
        钢结构加焊计算模型
        """
        area = (self.screen_wide + INCREMENT) * (self.screen_high + INCREMENT)
        quantity = area * MM_TO_M
        # 焊接为100元/平
        price = 100
        return dict(
            product_type='钢结构加焊不锈钢',
            brand='',
            price=price,
            quantity='%.2f' % quantity,
            total=quantity * price
        )

    def steel_structure_to_aluminum(self):
        """
        create_by ld_wu 2017-08-11
        钢架构换成铝单板计算模型
        """
        area = (self.screen_wide + INCREMENT) * (self.screen_high + INCREMENT)
        quantity = area * MM_TO_M
        # 加铝单板为150元/平
        price = 150
        return dict(
            product_type='钢结构换成铝单板',
            brand='',
            price=price,
            quantity='%.2f' % quantity,
            total=quantity * price
        )

    def power_line(self):
        """
        create_by ld_wu 2017-08-11
        电源线计算模型
        """
        product = self.__base_product('电源线')
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=self._power_line_count,
            total=self._power_line_count * product.suggest_price,
            product_type='电源线'
        )

    def cable(self):
        """
        create_by ld_wu 2017-08-11
        排线计算模型
        """
        product = self.__base_product('排线')
        quantity = self.m_wide_count * self.m_high_count

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='排线'
        )

    def three_m_reticle(self):
        """
        create_by ld_wu 2017-08-11
        3米网线计算模型
        ceil(总带载点数/650000)
        """
        product = self.__base_product('3米网线')
        # 高边点数
        h_total = self.m_high_count * self.module_high_resolution
        # 宽边点数
        w_total = self.m_wide_count * self.module_wide_resolution
        total = w_total * h_total
        quantity = math.ceil(total / SINGLE_RETICLE_PIXEL)

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='3米网线'
        )

    def one_and_half_m_reticle(self):
        """
        create_by ld_wu 2017-08-11
        1.5米网线计算模型
        """
        product = self.__base_product('1.5米网线')
        # 高边点数
        h_total = self.m_high_count * self.module_high_resolution
        # 宽边点数
        w_total = self.m_wide_count * self.module_wide_resolution
        total = w_total * h_total
        quantity = max(
            self._receive_count - math.ceil(total / SINGLE_RETICLE_PIXEL), 0
        )

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='1.5米网线'
        )

    def computer(self):
        """
        create_by ld_wu 2017-08-11
        电脑计算模型
        数量：只需要一台
        """
        product = self.__base_product('电脑')
        quantity = 1
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='电脑'
        )

    def exhaust_fan(self):
        """
        create_by ld_wu 2017-08-11
        排气扇计算模型
        数量：ceil(area/25) * 2 每25平方需要2台
        """
        area = self.screen_wide * self.screen_high * MM_TO_M
        ten_square_metre = 10
        if area > ten_square_metre:
            product = self.__base_product('排气扇')
            quantity = math.ceil(area / 25) * 2
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                product_type='排气扇'
            )
        return 0

    def air_conditioner(self):
        """
        create_by ld_wu 2017-08-11
        空调计算模型
        数量：ceil((area-20)/25)， 小于20平不需要， 大于20平每25平需要一台
        """
        area = self.screen_wide * self.screen_high * MM_TO_M
        ten_square_metre = 10
        if area > ten_square_metre:
            product = self.__base_product('空调')
            quantity = math.ceil((area - 20) / 25)
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                product_type='空调'
            )
        return 0

    def power_amplifier(self):
        """
        create_by ld_wu 2017-08-11
        功放计算模型
        数量： 只需要一个
        """
        product = self.__base_product('功放')
        quantity = 1
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='功放'
        )

    def sound_column(self):
        """
        create_by ld_wu 2017-08-11
        音柱计算模型
        数量： 只需要一个
        """
        product = self.__base_product('音柱')
        quantity = 1
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='音柱'
        )

    def lightning_rod(self):
        """
        create_by ld_wu 2017-08-11
        避雷针计算模型
        数量： 只需要一个
        """
        product = self.__base_product('避雷针')
        quantity = 1
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='避雷针'
        )

    def section_bar(self):
        """
        create_by ld_wu 2017-08-11
        型材计算模型
        """
        product = self.__base_product('型材')
        # 周长
        perimeter = (self.screen_high + self.screen_wide) * 2
        quantity = math.ceil(perimeter/SECTION_LENGTH)

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='型材'
        )

    def section_bar_corner(self):
        """
        create_by ld_wu 2017-08-11
        型材拐角计算模型
        """
        product = self.__base_product('型材拐角')
        # 型材拐角固定为4个
        corner_count = 4
        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=corner_count,
            total=corner_count * product.suggest_price,
            product_type='型材拐角'
        )

    def section_bar_interface(self):
        """
        create_by ld_wu 2017-08-11
        型材接口计算模型
        (floor(屏宽/6000) + floor(屏高/6000)) * 2
        """
        product = self.__base_product('型材接口')
        quantity = (
            math.floor(self.screen_wide/SECTION_LENGTH) +
            math.floor(self.screen_high/SECTION_LENGTH)
        ) * 2

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='型材接口'
        )

    def pendant(self):
        """
        create_by ld_wu 2017-08-11
        挂件计算模型
        数量为：ceil(屏体实际宽(米)) * 2 + 2
        """
        product = self.__base_product('挂件')
        # 屏宽需要从毫米换算成米
        quantity = math.ceil(self.screen_wide * Decimal('0.001')) * 2 + 2

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='挂件'
        )

    def square_pipe(self):
        """
        create_by ld_wu 2017-08-11
        方管计算模型
        ciel((宽边模组数 + 1) / floor(6000/屏高))
        """
        if self.screen_high <= SECTION_LENGTH:
            quantity = math.ceil(
                (self.m_wide_count + 1) /
                math.floor(SECTION_LENGTH / self.screen_high)
            )
            product_type = ProductType.get_by_args(name='方管')
            product = ProductTemplate.query.filter(
                ProductTemplate.product_type_id == product_type.id
            ).order_by(
                ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
            ).first()
            if not product:
                raise NotProductError('方管没有匹配的产品')
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                product_type='方管'
            )
        return dict()

    def special_square_pipe(self):
        """
        create_by ld_wu 2017-08-11
        特殊方管计算模型
        特殊方管为宽边模组数 + 1
        """
        if self.screen_high > SECTION_LENGTH:
            product_type = ProductType.get_by_args(name='特殊方管')
            product = ProductTemplate.query.filter(
                ProductTemplate.product_type_id == product_type.id
            ).order_by(
                ProductTemplate.weight.desc(), ProductTemplate.created_at.desc()
            ).first()
            if not product:
                raise NotProductError('特殊方管没有匹配的产品')
            quantity = self.m_wide_count + 1
            return dict(
                product_name=product.name,
                brand=product.brand,
                price=product.suggest_price,
                quantity=quantity,
                total=quantity * product.suggest_price,
                product_type='特殊方管'
            )
        return dict()

    def magnet(self):
        """
        create_by ld_wu 2017-08-11
        磁铁计算模型
        型材：数量为 4 * 模组数
        """
        product = self.__base_product('磁铁')
        # 单个模组需要的磁铁数
        single_count = 4
        quantity = self.m_wide_count * self.m_high_count * single_count

        return dict(
            product_name=product.name,
            brand=product.brand,
            price=product.suggest_price,
            quantity=quantity,
            total=quantity * product.suggest_price,
            product_type='磁铁'
        )

    # 户外计算集合
    out_door_compute = {
        module_group, original_box, rental_box, common_box,
        box_fastener, power_supply, receive_card, sending_card,
        video_processor, video_splicer, async_controller,
        network_card, screw, steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, lightning_rod
    }

    # 主要用于排序
    ALL = [
        module_group, original_box, rental_box, common_box,
        power_supply, box_fastener, receive_card, sending_card,
        video_processor, video_splicer, async_controller,
        network_card, screw, steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, magnet, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe, special_square_pipe,
        lightning_rod
    ]

    # 户内/全彩计算集合/墙挂/嵌入
    full_color_compute = in_door_compute = wall_compute = embed_compute = {
        module_group, original_box, rental_box, common_box,
        box_fastener, power_supply, receive_card, sending_card,
        video_processor, video_splicer, async_controller,
        network_card, screw, steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, magnet, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe, special_square_pipe,
        lightning_rod
    }

    # 单色/双色计算集合
    single_color_compute = double_color_compute = {
        module_group, original_box, common_box,
        box_fastener, power_supply, receive_card, sending_card,
        video_processor, video_splicer, async_controller,
        network_card, screw, steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, magnet, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe, special_square_pipe,
        lightning_rod
    }

    # 原装箱体计算集合
    original_box_compute = {
        box_fastener, sending_card, video_processor, video_splicer,
        network_card, steel_structure, steel_structure_high, original_box,
        steel_structure_welding, steel_structure_to_aluminum,
        three_m_reticle, one_and_half_m_reticle, computer, exhaust_fan,
        air_conditioner, power_amplifier, sound_column, lightning_rod, cable
    }

    # 租赁箱体计算集合
    rental_box_compute = {
        module_group, power_supply, receive_card, rental_box, box_fastener,
        sending_card, video_processor, video_splicer, network_card,
        screw, steel_structure, steel_structure_high, steel_structure_welding,
        steel_structure_to_aluminum, power_line, cable, three_m_reticle,
        one_and_half_m_reticle, computer, exhaust_fan, power_amplifier,
        sound_column, lightning_rod
    }

    # 普通箱体计算集合
    common_box_compute = {
        module_group, box_fastener, power_supply, receive_card, common_box,
        sending_card, video_processor, video_splicer, network_card,
        screw, steel_structure, steel_structure_high, steel_structure_welding,
        steel_structure_to_aluminum, power_line, cable, three_m_reticle,
        one_and_half_m_reticle, computer, exhaust_fan, power_amplifier,
        sound_column, lightning_rod
    }

    # 落地计算集合
    ground_compute = {
        module_group, original_box, common_box, rental_box, box_fastener,
        power_supply, receive_card, sending_card, video_processor,
        steel_structure, steel_structure_welding, steel_structure_to_aluminum,
        video_splicer, async_controller, network_card, screw, power_line, cable,
        three_m_reticle, one_and_half_m_reticle, computer, exhaust_fan,
        air_conditioner, power_amplifier, sound_column, lightning_rod
    }

    # 单立柱/双立柱
    single_column_compute = double_column_compute = {
        module_group, original_box, rental_box, common_box, box_fastener,
        power_supply, receive_card, sending_card, video_processor,
        video_splicer, network_card, steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum, power_line,
        screw, three_m_reticle, one_and_half_m_reticle, computer,
        power_amplifier, exhaust_fan, air_conditioner,
        sound_column, lightning_rod
    }

    # # 租赁
    # rental_compute = {
    #     module_group, original_box, rental_box, common_box, box_fastener,
    #     power_supply, receive_card, sending_card, video_processor,
    #     steel_structure, video_splicer, async_controller,
    #     screw, power_line, cable, three_m_reticle,
    #     one_and_half_m_reticle, computer, exhaust_fan,
    #     air_conditioner, power_amplifier, sound_column, lightning_rod
    # }

    # 同步
    sync_compute = {
        module_group, original_box, rental_box, common_box,
        box_fastener, power_supply, receive_card, sending_card,
        video_processor, video_splicer, network_card, screw,
        steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, magnet, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe, special_square_pipe,
        lightning_rod
    }

    # 异步
    async_compute = {
        module_group, original_box, rental_box, common_box,
        box_fastener, power_supply, receive_card,
        sending_card, network_card, screw,
        steel_structure, steel_structure_high,
        steel_structure_welding, steel_structure_to_aluminum,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        sound_column, magnet, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe, special_square_pipe,
        lightning_rod
    }

    # 门头异步
    door_async_compute = {
        module_group, power_supply, async_controller, section_bar,
        section_bar_corner, section_bar_interface, pendant,
        square_pipe, special_square_pipe, magnet, steel_structure,
        steel_structure_high, steel_structure_welding,
        steel_structure_to_aluminum, power_line, cable
    }

    # 型材
    section_bar_compute = {
        module_group, power_supply, receive_card, sending_card,
        video_processor, video_splicer, async_controller,
        network_card, section_bar, section_bar_corner,
        section_bar_interface, pendant, square_pipe,
        special_square_pipe, magnet, steel_structure,
        steel_structure_to_aluminum, steel_structure_welding,
        power_line, cable, three_m_reticle, one_and_half_m_reticle,
        computer, exhaust_fan, air_conditioner, power_amplifier,
        lightning_rod
    }

    IN_OR_OUT_MAP = {
        SceneType.INDOOR: in_door_compute,
        SceneType.OUTDOOR: out_door_compute
    }

    BOX_MAP = {
        AssembleWay.ORIGINAL: original_box_compute,
        AssembleWay.RENTAL: rental_box_compute,
        AssembleWay.COMMON: common_box_compute,
        AssembleWay.SECTION: section_bar_compute
    }

    TRANSPORT_MAP = {
        Transport.ASYNC: async_compute,
        Transport.SYNC: sync_compute,
        Transport.DOOR_ASYNC: door_async_compute
    }

    COLOR_MAP = {
        ColorType.SINGLE: single_color_compute,
        ColorType.DOUBLE: double_color_compute,
        ColorType.FULL: full_color_compute
    }

    INSTALL_WAY_MAP = {
        InstallWay.WALL: wall_compute,
        # InstallWay.EMBED: embed_compute,
        InstallWay.DOUBLE: double_column_compute,
        InstallWay.SINGLE: single_column_compute,
        # InstallWay.RENTAL: rental_compute,
        InstallWay.GROUND: ground_compute,
        # InstallWay.HANG: suspension_compute
    }

    def union_compute_rules(self):
        """
        create_by ld_wu 2017-08-14
        从输入条件获取相应的组合计算集合
        """
        # FIXME by ld_wu 2017-08-14 只做了模组计算
        scene_compute = self.IN_OR_OUT_MAP.get(self._scene_type, set())
        box_compute = self.BOX_MAP.get(self._assemble_way, set())
        transport_compute = self.TRANSPORT_MAP.get(self._transport, set())
        color_compute = self.COLOR_MAP.get(self._color, set())
        install_compute = self.INSTALL_WAY_MAP.get(self._install_way, set())
        return (
            scene_compute &
            box_compute &
            transport_compute &
            color_compute &
            install_compute
        )

    def reduce_compute(self, rules):
        """
        汇总各项计算结果
        """
        rules = sorted(rules, key=lambda x: self.ALL.index(x))
        return [rule(self) for rule in rules]

    def quotation_models(self):
        """
        create_by ld_wu 2017-08-14
        报价计算模型
        """
        rules = self.union_compute_rules()
        return self.reduce_compute(rules)




